﻿using OSGeo.GDAL;
using OSGeo.OGR;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp
{
    public class Gdal2tilesHelper
    {
        public const string TILE_FORMAT = "png";
        public const string GRID_METADATA_JSON_FILE_NAME = "tilemap.json";
        public static void GenerateTiles(string inputPath, string outputDir,
                                        int minZoom = 0, int maxZoom = -1,
                                        int tileSize = 256, // 添加瓦片大小参数
                                        ResamplingMethod resampling = ResamplingMethod.Cubic,
                                        TileScheme scheme = TileScheme.XYZ,
                                        bool verbose = false)
        {
            if (!File.Exists(inputPath))
                throw new ArgumentException("输入文件不存在");

            if (Directory.Exists(outputDir))
            {
                // 清空输出目录
                Directory.Delete(outputDir, true);
            }
            Directory.CreateDirectory(outputDir);

            // 配置GDAL
            GdalConfiguration.ConfigureGdal();
            GdalConfiguration.ConfigureOgr();
            Gdal.AllRegister();
            Ogr.RegisterAll();
            // 强制写在外部 ovr 文件，而不是源文件内部
            //Gdal.SetConfigOption("USE_RRD", "NO");
            //Gdal.SetConfigOption("HFA_USE_RRD", "YES");
            Gdal.SetConfigOption("GDAL_TIFF_OVR", "YES");
            Gdal.SetConfigOption("OVERVIEW_WRITE_FORMAT", "GTiff");
            using (Dataset dataset = Gdal.Open(inputPath, Access.GA_ReadOnly))
            {
                if (dataset == null)
                    throw new InvalidOperationException($"无法读取文件: {inputPath}");

                // 获取地理信息
                double[] transform = new double[6];
                dataset.GetGeoTransform(transform);
                string projection = dataset.GetProjection();

                // 计算缩放级别范围
                if (maxZoom == -1)
                {
                    maxZoom = CalculateMaxZoomLevel(dataset, tileSize); // 传入瓦片大小
                }

                if (verbose)
                {
                    Console.WriteLine($"输入文件: {inputPath}");
                    Console.WriteLine($"输出目录: {outputDir}");
                    Console.WriteLine($"缩放级别: {minZoom} 到 {maxZoom}");
                    Console.WriteLine($"瓦片大小: {tileSize}×{tileSize}");
                    Console.WriteLine($"投影: {projection}");
                    Console.WriteLine($"地理变换: {string.Join(", ", transform)}");
                }

                // 创建金字塔级别的概览
                CreateOverviewPyramid(dataset, minZoom, maxZoom, resampling, tileSize, verbose);

                // 为每个缩放级别生成瓦片
                for (int zoom = minZoom; zoom <= maxZoom; zoom++)
                {
                    if (verbose) Console.WriteLine($"处理缩放级别 {zoom}");
                    ProcessZoomLevel(dataset, zoom, maxZoom, outputDir, scheme, resampling, tileSize, verbose);
                }

                // 生成元数据文件
                GenerateMetadata(dataset, outputDir, minZoom, maxZoom, tileSize, scheme);
            }
        }

        // 计算最大缩放级别 - 修改为考虑瓦片大小
        private static int CalculateMaxZoomLevel(Dataset dataset, int tileSize)
        {
            int width = dataset.RasterXSize;
            int height = dataset.RasterYSize;

            int maxZoom = 0;
            while (width > tileSize || height > tileSize)
            {
                width = (width + 1) / 2;
                height = (height + 1) / 2;
                maxZoom++;
            }

            return maxZoom;
        }
        // 创建金字塔概览 - 修改为考虑瓦片大小
        // 创建金字塔级别的概览 - 修改为考虑瓦片大小
        private static void CreateOverviewPyramid(Dataset dataset, int minZoom, int maxZoom,
                                                ResamplingMethod resampling, int tileSize, bool verbose)
        {
            // 计算需要的概览级别
            List<int> overviewLevels = new List<int>();
            for (int zoom = minZoom + 1; zoom <= maxZoom; zoom++)
            {
                int level = (int)Math.Pow(2, maxZoom - zoom);
                if (!overviewLevels.Contains(level))
                {
                    overviewLevels.Add(level);
                }
            }

            if (overviewLevels.Count > 0)
            {
                if (verbose) Console.WriteLine("创建金字塔概览...");

                // 转换为GDAL可接受的概览级别数组
                int[] levels = overviewLevels.OrderBy(l => l).ToArray();

                // 设置重采样方法
                string resampleMethod = GetGdalResampleMethod(resampling);

                try
                {
                   
                    // 在数据集上构建概览，而不是在单个波段上
                    dataset.BuildOverviews(resampleMethod, levels, null, null);

                    if (verbose)
                    {
                        Console.WriteLine($"成功创建概览级别: {string.Join(", ", levels)}");

                        // 验证概览是否创建成功
                        for (int i = 1; i <= dataset.RasterCount; i++)
                        {
                            using (Band band = dataset.GetRasterBand(i))
                            {
                                int overviewCount = band.GetOverviewCount();
                                Console.WriteLine($"波段 {i} 有 {overviewCount} 个概览");
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"创建概览时出错: {ex.Message}");
                }
            }
            else if (verbose)
            {
                Console.WriteLine("不需要创建概览");
            }
        }

        private static void ProcessZoomLevel(Dataset dataset, int zoom, int maxZoom, string outputDir,
                                           TileScheme scheme, ResamplingMethod resampling,
                                           int tileSize, bool verbose)
        {
            // 计算此级别的瓦片网格
            int tilesX = (int)Math.Ceiling(dataset.RasterXSize / (tileSize * Math.Pow(2, maxZoom - zoom)));
            int tilesY = (int)Math.Ceiling(dataset.RasterYSize / (tileSize * Math.Pow(2, maxZoom - zoom)));

            // 创建缩放级别目录
            string zoomDir = Path.Combine(outputDir, zoom.ToString());
            Directory.CreateDirectory(zoomDir);

            // 并行处理瓦片
            Parallel.For(0, tilesX, x =>
            {
                string xDir = Path.Combine(zoomDir, x.ToString());
                Directory.CreateDirectory(xDir);

                for (int y = 0; y < tilesY; y++)
                {
                    // 根据瓦片方案调整Y坐标
                    int actualY = scheme == TileScheme.TMS ? ((1 << zoom) - 1 - y) : y;

                    GenerateTile(dataset, zoom, x, actualY, xDir, resampling, tileSize, verbose);
                }
            });
        }

        // 生成单个瓦片 - 修改为考虑瓦片大小
        private static void GenerateTile(Dataset dataset, int zoom, int x, int y,
                                       string xDir, ResamplingMethod resampling,
                                       int tileSize, bool verbose)
        {
            // 计算此瓦片在原始图像中的像素范围
            int pixelX = x * tileSize;
            int pixelY = y * tileSize;

            // 计算实际读取区域
            int readWidth = Math.Min(tileSize, dataset.RasterXSize - pixelX);
            int readHeight = Math.Min(tileSize, dataset.RasterYSize - pixelY);

            if (readWidth <= 0 || readHeight <= 0) return;

            // 生成文件名
            string fileName = $"{y}.{TILE_FORMAT}";
            string filePath = Path.Combine(xDir, fileName);

            // 检查是否已存在
            if (File.Exists(filePath)) return;

            // 读取瓦片数据
            byte[][] tileData = ReadTileData(dataset, pixelX, pixelY, readWidth, readHeight, resampling, tileSize);

            // 检查是否为空白瓦片
            if (IsTileBlank(tileData))
            {
                if (verbose) Console.WriteLine($"跳过空白瓦片 {zoom}/{x}/{y}");
                return;
            }

            // 保存瓦片
            SaveTileImage(tileData, readWidth, readHeight, filePath, tileSize);

            if (verbose) Console.WriteLine($"生成瓦片 {zoom}/{x}/{y}");
        }

        // 读取瓦片数据 - 修改为考虑瓦片大小
        private static byte[][] ReadTileData(Dataset dataset, int x, int y, int width, int height,
                                           ResamplingMethod resampling, int tileSize)
        {
            int bandCount = dataset.RasterCount;
            byte[][] data = new byte[bandCount][];

            string resampleMethod = GetGdalResampleMethod(resampling);

            for (int i = 0; i < bandCount; i++)
            {
                using (Band band = dataset.GetRasterBand(i + 1))
                {
                    data[i] = new byte[width * height];

                    // 使用指定的重采样方法读取数据
                    // 对于大瓦片，可能需要分块读取以提高性能
                    if (tileSize > 512)
                    {
                        // 分块读取大瓦片
                        ReadTileDataInBlocks(band, x, y, width, height, data[i], resampleMethod);
                    }
                    else
                    {
                        // 直接读取小瓦片
                        band.ReadRaster(x, y, width, height, data[i], width, height, 0, 0);
                    }
                }
            }

            return data;
        }
        // 分块读取大瓦片数据 - 新增方法
        private static void ReadTileDataInBlocks(Band band, int x, int y, int width, int height, 
                                               byte[] buffer, string resampleMethod, int blockSize = 256)
        {
            // 计算分块数量
            int blocksX = (int)Math.Ceiling((double)width / blockSize);
            int blocksY = (int)Math.Ceiling((double)height / blockSize);
            
            // 分块读取数据
            for (int blockY = 0; blockY < blocksY; blockY++)
            {
                for (int blockX = 0; blockX < blocksX; blockX++)
                {
                    // 计算当前块的起始位置和大小
                    int blockStartX = x + blockX * blockSize;
                    int blockStartY = y + blockY * blockSize;
                    int blockWidth = Math.Min(blockSize, width - blockX * blockSize);
                    int blockHeight = Math.Min(blockSize, height - blockY * blockSize);
                    
                    // 计算缓冲区中的偏移量
                    int bufferOffset = blockY * blockSize * width + blockX * blockSize;
                    
                    // 读取当前块
                    byte[] blockData = new byte[blockWidth * blockHeight];
                    band.ReadRaster(blockStartX, blockStartY, blockWidth, blockHeight, 
                                  blockData, blockWidth, blockHeight, 0, 0);
                    
                    // 将块数据复制到缓冲区
                    for (int row = 0; row < blockHeight; row++)
                    {
                        Array.Copy(blockData, row * blockWidth, 
                                 buffer, bufferOffset + row * width, 
                                 blockWidth);
                    }
                }
            }
        }

        // 保存瓦片图像 - 修改为考虑瓦片大小
        private static void SaveTileImage(byte[][] tileData, int width, int height, string filePath, int tileSize)
        {
            int bandCount = tileData.Length;

            // 对于大瓦片，使用更高效的图像处理方式
            if (tileSize > 512)
            {
                SaveLargeTileImage(tileData, width, height, filePath);
            }
            else
            {
                SaveSmallTileImage(tileData, width, height, filePath);
            }
        }

        // 保存小瓦片图像
        private static void SaveSmallTileImage(byte[][] tileData, int width, int height, string filePath)
        {
            int bandCount = tileData.Length;

            using (Bitmap bmp = new Bitmap(width, height,
                bandCount >= 3 ? PixelFormat.Format24bppRgb : PixelFormat.Format8bppIndexed))
            {
                if (bandCount >= 3)
                {
                    // RGB图像
                    BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height),
                        ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

                    unsafe
                    {
                        byte* ptr = (byte*)bmpData.Scan0;
                        for (int i = 0; i < width * height; i++)
                        {
                            ptr[2] = tileData[0][i]; // R
                            ptr[1] = tileData[1][i]; // G
                            ptr[0] = tileData[2][i]; // B
                            ptr += 3;
                        }
                    }

                    bmp.UnlockBits(bmpData);
                }
                else
                {
                    // 单波段图像，使用灰度
                    BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height),
                        ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);

                    // 设置灰度调色板
                    ColorPalette palette = bmp.Palette;
                    for (int i = 0; i < 256; i++)
                    {
                        palette.Entries[i] = Color.FromArgb(i, i, i);
                    }
                    bmp.Palette = palette;

                    // 复制数据
                    Marshal.Copy(tileData[0], 0, bmpData.Scan0, width * height);
                    bmp.UnlockBits(bmpData);
                }

                bmp.Save(filePath, ImageFormat.Png);
            }
        }

        // 保存大瓦片图像 - 新增方法
        private static void SaveLargeTileImage(byte[][] tileData, int width, int height, string filePath)
        {
            int bandCount = tileData.Length;

            // 对于大瓦片，使用更高效的图像处理方式
            // 这里可以使用更专业的图像处理库，如ImageSharp
            // 或者使用GDAL直接输出图像

            // 这里使用GDAL创建内存数据集并输出PNG
            OSGeo.GDAL.Driver memDriver = Gdal.GetDriverByName("MEM");
            OSGeo.GDAL.Driver pngDriver = Gdal.GetDriverByName("PNG");

            using (Dataset memDataset = memDriver.Create("", width, height, bandCount, DataType.GDT_Byte, null))
            {
                // 写入数据
                for (int i = 0; i < bandCount; i++)
                {
                    using (Band band = memDataset.GetRasterBand(i + 1))
                    {
                        band.WriteRaster(0, 0, width, height, tileData[i], width, height, 0, 0);
                    }
                }

                // 保存为PNG
                pngDriver.CreateCopy(filePath, memDataset, 0, null, null, null);
            }
        }

        // 生成元数据文件 - 修改为包含瓦片大小信息
        private static void GenerateMetadata(Dataset dataset, string outputDir,
                                           int minZoom, int maxZoom, int tileSize, TileScheme scheme)
        {
            double[] transform = new double[6];
            dataset.GetGeoTransform(transform);

            // 计算边界框
            double minX = transform[0];
            double maxX = transform[0] + dataset.RasterXSize * transform[1];
            double minY = transform[3] + dataset.RasterYSize * transform[5];
            double maxY = transform[3];

            // 获取投影信息
            string projection = dataset.GetProjection();

            // 创建元数据对象
            var metadata = new
            {
                name = Path.GetFileNameWithoutExtension(dataset.GetDescription()),
                description = "Tiles generated by C# Gdal2Tiles",
                version = "1.0.0",
                format = TILE_FORMAT,
                tileSize = tileSize, // 包含瓦片大小信息
                minZoom = minZoom,
                maxZoom = maxZoom,
                bounds = new[] { minX, minY, maxX, maxY },
                center = new[] { (minX + maxX) / 2, (minY + maxY) / 2 },
                projection = projection,
                scheme = scheme.ToString().ToLower(),
                tiles = new[] { "{z}/{x}/{y}.png" }
            };

            // 保存为JSON
            string json = Newtonsoft.Json.JsonConvert.SerializeObject(metadata, Newtonsoft.Json.Formatting.Indented);
            File.WriteAllText(Path.Combine(outputDir, GRID_METADATA_JSON_FILE_NAME), json);
        }

        private static ResamplingMethod ParseResamplingMethod(string method)
        {
            switch (method.ToLower())
            {
                case "nearest": return ResamplingMethod.NearestNeighbor;
                case "bilinear": return ResamplingMethod.Bilinear;
                case "cubic": return ResamplingMethod.Cubic;
                case "cubicspline": return ResamplingMethod.CubicSpline;
                case "lanczos": return ResamplingMethod.Lanczos;
                default: return ResamplingMethod.Cubic;
            }
        }

        private static TileScheme ParseTileScheme(string scheme)
        {
            switch (scheme.ToLower())
            {
                case "xyz": return TileScheme.XYZ;
                case "tms": return TileScheme.TMS;
                default: return TileScheme.XYZ;
            }
        }
        // 获取GDAL重采样方法字符串
        private static string GetGdalResampleMethod(ResamplingMethod method)
        {
            switch (method)
            {
                case ResamplingMethod.NearestNeighbor: return "NEAREST";
                case ResamplingMethod.Bilinear: return "BILINEAR";
                case ResamplingMethod.Cubic: return "CUBIC";
                case ResamplingMethod.CubicSpline: return "CUBICSPLINE";
                case ResamplingMethod.Lanczos: return "LANCZOS";
                default: return "CUBIC";
            }
        }
        // 检查瓦片是否为空白
        private static bool IsTileBlank(byte[][] tileData)
        {
            if (tileData == null || tileData.Length == 0) return true;

            int bandCount = tileData.Length;
            int pixelCount = tileData[0].Length;

            // 检查所有波段的所有像素是否一致（可能是空白区域）
            for (int b = 0; b < bandCount; b++)
            {
                byte[] data = tileData[b];
                if (data.Length != pixelCount) return false;

                byte firstValue = data[0];
                for (int i = 1; i < pixelCount; i++)
                {
                    if (data[i] != firstValue) return false;
                }
            }

            return true;
        }
    }
    // 重采样方法
    public enum ResamplingMethod
    {
        NearestNeighbor,
        Bilinear,
        Cubic,
        CubicSpline,
        Lanczos
    }
    // 瓦片方案
    public enum TileScheme
    {
        XYZ,        // 标准的XYZ方案 (Google Maps兼容)
        TMS         // 瓦片地图服务方案
    }
}
